package app

import (
	"context"
	"errors"
	"go.uber.org/zap"
	"golang.org/x/sync/errgroup"
	"os"
	"os/signal"
	"sync"
	"syscall"
	"time"
	"wzgames/internal/model"
	"wzgames/pkg/common/component"
	"wzgames/pkg/components/grabbitmq"
	"wzgames/pkg/components/mysql"
	"wzgames/pkg/components/redis"
	"wzgames/pkg/logger"
)

var (
	_app *App
)

// Application

type App struct {
	opts   options
	ctx    context.Context
	cancel func()
	// 启动配置
	bootc *model.GBoot
	// 组件注册管理
	compmgr *component.Componentry
	mu      sync.RWMutex
}

func New(opts ...Option) *App {
	o := options{
		ctx:         context.Background(),
		signals:     []os.Signal{os.Interrupt, syscall.SIGHUP, syscall.SIGTERM, syscall.SIGQUIT, syscall.SIGINT, /*syscall.SIGUSR2,*/ syscall.SIGKILL},
		stopTimeout: 10 * time.Second,
	}
	for _, opt := range opts {
		opt(&o)
	}

	ctx, cancel := context.WithCancel(o.ctx)
	return &App{
		ctx:     ctx,
		cancel:  cancel,
		opts:    o,
		compmgr: component.NewComponentry(),
	}
}

func (a *App) initialize(bootc *model.GBoot) {
	a.bootc = bootc

	loc, err := time.LoadLocation(bootc.Timezone())
	if err != nil {
		loc = time.Local
	}
	TimeLocation(loc)

	// reg components
	_ = a.compmgr.RegisterComponent(&mysql.Component{})
	_ = a.compmgr.RegisterComponent(&redis.Component{})
	_ = a.compmgr.RegisterComponent(&grabbitmq.Component{})

	AfterStop(func(ctx context.Context) error {
		er := a.compmgr.DestroyAll()
		return er
	})
}

func (a *App) Start() error {
	var err error
	sctx := a.ctx
	eg, ctx := errgroup.WithContext(sctx)
	wg := sync.WaitGroup{}

	for _, fn := range a.opts.beforeStart {
		if err = fn(sctx); err != nil {
			return err
		}
	}
	for _, srv := range a.opts.servers {
		_srv := srv
		eg.Go(func() error {
			<-ctx.Done()
			stopCtx, cancel := context.WithTimeout(a.opts.ctx, a.opts.stopTimeout)
			defer cancel()
			return _srv.Stop(stopCtx)
		})
		wg.Add(1)
		eg.Go(func() error {
			wg.Done()
			return _srv.Start(sctx)
		})
	}
	wg.Wait()

	for _, fn := range a.opts.afterStart {
		if err = fn(sctx); err != nil {
			return err
		}
	}

	signalChan := make(chan os.Signal, 1)
	signal.Notify(signalChan, a.opts.signals...)
	eg.Go(func() error {
		select {
		case <-ctx.Done():
			return nil
		case sig := <-signalChan:
			switch sig {
			//case syscall.SIGUSR2:
				// TODO
				//return nil
			default:
				return a.Stop()
			}
		}
	})
	if err = eg.Wait(); err != nil && !errors.Is(err, context.Canceled) {
		return err
	}
	for _, fn := range a.opts.afterStop {
		err = fn(sctx)
	}
	return err
}

func (a *App) Stop() (err error) {
	sctx := a.ctx
	for _, fn := range a.opts.beforeStop {
		err = fn(sctx)
	}

	if a.cancel != nil {
		a.cancel()
	}
	return
}

func Start(bootc *model.GBoot, opts ...Option) error {
	if bootc == nil {
		return errors.New("boot config miss")
	}
	_app = New(opts...)
	_app.initialize(bootc)
	err := _app.Start()
	if err != nil {
		logger.Error("[GApp] app start fail", zap.Error(err))
		return err
	}

	return nil
}

func Comp() *component.Componentry {
	return _app.compmgr
}
